<!--
 * @Description: 上传组件
 * @Author: DHL
 * @Date: 2022-08-29 10:48:53
 * @LastEditors: DHL
 * @LastEditTime: 2023-01-10 15:56:48

 使用说明：
 方式一：有字典
 <TwUpload
    ref="twUploadRef"
    fileType=".jpg,.jpeg,.png,.gif,.pdf"
    bizTypeKey="ROLE_TYPE"
    :maxNum="2"
  ></TwUpload>

  方式二：无字典
  <TwUpload
    ref="twUploadRef"
    fileType=".jpg,.jpeg,.png,.gif,.pdf"
    :bizTypes="[{ name: '类别1', value: 'type1' }]"
    :maxNum="2"
  ></TwUpload>

  nextTick(() => {
    twUploadRef.value.loadData({ bizId: 'abc' })
  })

  // 表单提交时获取提交参数
  twUploadRef.value.getFileIds()
-->
<template>
  <div class="tw-upload-wrapper">
    <div class="header" v-if="['add', 'edit'].includes(action)">
      <div class="toolbar">
        <div class="file-biz-type" v-if="showBizTypes">
          <el-select v-model="bizType" clearable placeholder="请选择文件类别">
            <template v-if="bizTypeKey">
              <tw-dic :dicKey="bizTypeKey"></tw-dic>
            </template>
            <template v-else>
              <el-option
                v-for="item in bizTypes"
                :key="item.value"
                :label="item.name"
                :value="item.value"
              ></el-option>
            </template>
          </el-select>
        </div>

        <div class="btns">
          <el-upload
            class="tw-upload"
            multiple
            :show-file-list="false"
            :action="state.url"
            :headers="state.headers"
            :limit="maxNum"
            :accept="fileType"
            :data="upFileParams"
            :before-upload="handleBeforeUpload"
            :on-exceed="handleExceed"
            :on-change="handleChange"
            :disabled="upBtnDisabled"
          >
            <el-button type="primary" :disabled="upBtnDisabled">
              <svg-icon className="icon-upload" icon-class="cloudUpload"></svg-icon>
              选择文件
            </el-button>
          </el-upload>
        </div>
      </div>

      <div class="tip">
        请上传小于【{{ maxFileSize }} KB】的【{{ fileType }}】文件，允许上传的最大数量【{{
          maxNum
        }}】
      </div>
    </div>

    <div class="content" v-if="state.fileList && state.fileList.length > 0">
      <el-table :data="state.fileList" stripe style="width: 100%">
        <el-table-column prop="name" label="文件名称" />
        <el-table-column prop="raw.bizTypeName" label="文件类型" />
        <el-table-column prop="percentage" label="进度">
          <template #default="scope">
            {{ scope.row.percentage > 100 ? 100 : scope.row.percentage }} %
          </template>
        </el-table-column>
        <el-table-column fixed="right" label="操作" width="150">
          <template #default="scope">
            <div class="btn-group">
              <div class="btn" @click="handlePreview(scope.row)">预览</div>
              <div class="btn" @click="handleDownload(scope.row)">下载</div>
              <div
                class="btn"
                @click="handleDelete(scope.row)"
                v-if="['add', 'edit'].includes(action)"
              >
                删除
              </div>
            </div>
          </template>
        </el-table-column>
      </el-table>
    </div>
  </div>
</template>

<script setup lang="ts">
  import { stringUtils } from '/@/utils/stringUtils'
  import { useNetworkConfig } from '/@/hooks/setting'
  import { useLoginUserStore } from '/@/store/modules/loginUser'
  import { ElMessage, UploadFile, UploadFiles, UploadRawFile } from 'element-plus'
  import { assign, cloneDeep } from 'lodash-es'
  import { useCacheData } from '/@/hooks/web/cacheData'

  import { api as viewerApi } from 'v-viewer'
  import { apiFileBatchDelete, apiFileDownload, apiFileSearch } from '/@/api/common/fileCenter'
  import { arrayRemoveByPropertyValue } from '/@/utils/twUtils'
  import { log } from '/@/utils/log'
  import BigNum from '/@/utils/bigNum'

  const loginUserStore = useLoginUserStore()
  const cacheData = useCacheData()

  //////////////////////////////////////////////////
  // 属性
  //////////////////////////////////////////////////
  interface Props {
    /**
     * 操作类型【add|view】
     */
    action: string

    /**
     * 字典KEY[TYPE_CODE]
     */
    bizTypeKey?: Nullable<string>

    /**
     * 业务分类
     */
    bizTypes?: Array<{ name: string; value: string }>

    /**
     * 接受上传的文件类型
     */
    fileType: string

    /**
     * 允许上传文件的最大数量
     */
    maxNum?: number

    /**
     * 允许上传文件的最大KB
     */
    maxFileSize?: number

    /**
     * 上传时附带的额外参数
     */
    params?: object
  }

  const props = withDefaults(defineProps<Props>(), {
    action: 'add',
    bizTypeKey: null,
    bizTypes: () => {
      return []
    },
    fileType: '.jpg,.jpeg,.png,.gif,.pdf',
    maxNum: 1,
    maxFileSize: 5120,
    params: () => {
      return {
        folder: 'temp'
      }
    }
  })

  /**
   * 是否显示文件类别下拉框
   */
  const showBizTypes = computed(() => {
    if (stringUtils.isNotEmpty(props.bizTypeKey)) {
      const bizTypeList = cacheData.getDic(props.bizTypeKey || '')
      if (bizTypeList && bizTypeList.length > 1) {
        return true
      }
    }

    if (props.bizTypes && props.bizTypes.length > 1) {
      return true
    }

    return false
  })

  /**
   * 是否禁用上传按钮
   */
  const upBtnDisabled = computed(() => {
    if (showBizTypes.value && stringUtils.isEmpty(bizType.value)) {
      return true
    }

    return false
  })

  /**
   * 业务分类
   */
  const bizType = ref('')

  /**
   * 状态
   */
  const state = reactive({
    url: `${useNetworkConfig.baseURL}/fileCenter/uploadFiles`,
    headers: {
      'X-AppId': loginUserStore.appId,
      'X-AppToken': loginUserStore.appToken,
      'X-AppSecret': loginUserStore.appSecret,
      'X-Token': loginUserStore.token
    },
    fileList: [] as any
  })

  /**
   * 上传时附带的额外参数
   */
  const upFileParams = computed(() => {
    const params = cloneDeep(props.params)

    // 业务类型
    let bizTypeParams = {}

    if (stringUtils.isNotEmpty(bizType.value)) {
      // 文件类型下拉选择后，提交参数
      let bizTypeName = ''

      if (props.bizTypeKey && stringUtils.isNotEmpty(props.bizTypeKey)) {
        bizTypeName = cacheData.getDicName(props.bizTypeKey, bizType.value) || ''
      } else {
        props.bizTypes.forEach((item) => {
          if (item.value === bizType.value) {
            bizTypeName = item.name
          }
        })
      }

      bizTypeParams = {
        bizType: bizType.value,
        bizTypeName: bizTypeName
      }
    } else {
      // 如果文件类型下拉只有一个选项，默认取第一个提交
      if (stringUtils.isNotEmpty(props.bizTypeKey)) {
        const bizTypeList = cacheData.getDic(props.bizTypeKey || '')
        if (bizTypeList && bizTypeList.length === 1) {
          const bizTypeObj = bizTypeList[0]
          bizTypeParams = {
            bizType: bizTypeObj.dataValue,
            bizTypeName: bizTypeObj.dataName
          }
        }
      } else if (props.bizTypes && props.bizTypes.length === 1) {
        const bizTypeObj = props.bizTypes[0]
        bizTypeParams = {
          bizType: bizTypeObj.value,
          bizTypeName: bizTypeObj.name
        }
      }
    }

    assign(params, bizTypeParams)

    return params
  })

  //////////////////////////////////////////////////
  // 函数
  //////////////////////////////////////////////////

  /**
   * 文件状态改变时的钩子，添加文件、上传成功和上传失败时都会被调用
   * @param uploadFile
   * @param uploadFiles
   */
  function handleChange(uploadFile: UploadFile, uploadFiles: UploadFiles) {
    const isExist = state.fileList.some(
      (item: { uid: string }) => item.uid.toString() === uploadFile.uid.toString()
    )

    if (!isExist) {
      state.fileList.push(uploadFile)
    }
  }

  /**
   * 上传文件之前的钩子，参数为上传的文件， 若返回false或者返回 Promise 且被 reject，则停止上传
   * @param rawFile
   */
  function handleBeforeUpload(rawFile: UploadRawFile) {
    if (!rawFile) {
      return true
    }

    // 上传文件超过限制数
    if (state.fileList.length > props.maxNum) {
      ElMessage.error(`允许最多上传 ${props.maxNum} 个文件`)
      fileListRemove(rawFile)
      return false
    }

    const fileSize = new BigNum(rawFile.size)
    fileSize.divide(1024)

    if (fileSize.parse() > props.maxFileSize) {
      ElMessage.error(`上传文件不能超过${props.maxFileSize}KB`)
      fileListRemove(rawFile)
      return false
    }

    // 文件类型
    const upParams = upFileParams.value as any
    assign(rawFile, {
      bizType: upParams.bizType,
      bizTypeName: upParams.bizTypeName
    })

    return true
  }

  /**
   * 选择文件数超过限制数
   */
  function handleExceed(files, uploadFiles) {
    ElMessage.error(`允许最多上传 ${props.maxNum} 个文件`)
  }

  /**
   * 预览
   */
  function handlePreview(file: any) {
    if (file && file.percentage < 100) {
      ElMessage.warning('请等待文件上传完成后再预览')
      return
    }

    // 当前预览的文件
    let myFile: any = null

    if (file.response.data && file.response.data.length > 0) {
      myFile = file.response.data[0]
    }

    if (myFile == null) {
      ElMessage.warning('文件不支持预览')
      return
    }

    if (myFile && myFile.fileType.indexOf('image') > -1) {
      // 图片预览

      const imageList: Array<any> = []
      let thumbnailUrl = myFile.thumbnailFilePath
      if (stringUtils.isEmpty(thumbnailUrl)) {
        thumbnailUrl = `${myFile.filePath}?width=200`
      }

      imageList.push({
        src: thumbnailUrl,
        'data-source': myFile.filePath
      })

      viewerApi({
        options: {
          toolbar: true,
          url: 'data-source',
          initialViewIndex: 0,
          zIndex: 6000
        },
        images: imageList
      })
    } else {
      // 其他文件

      window.open(myFile.url, '_blank')
    }
  }

  /**
   * 文件下载
   */
  function handleDownload(file: any) {
    // 当前的文件
    let myFile: any = null

    if (file.response.data && file.response.data.length > 0) {
      myFile = file.response.data[0]
    }

    if (!myFile) {
      log.error('下载文件为空')
      return
    }

    apiFileDownload(myFile.fileId, myFile.oldName, (progressParam: any) => {
      if (progressParam.loaded && progressParam.loaded > 0) {
        file.percentage = new BigNum(progressParam.loaded)
          .divide(progressParam.total)
          .multiply(100)
          .toFixed(2)
          .parse()
      }
    })
  }

  /**
   * 文件逻辑删除
   */
  function handleDelete(file: any) {
    // 当前的文件
    let myFile: any = null

    if (file.response.data && file.response.data.length > 0) {
      myFile = file.response.data[0]
    }

    if (myFile) {
      apiFileBatchDelete(myFile.fileId).then((result) => {
        if (result?.success) {
          fileListRemove(file)

          ElMessage.success(result.message)
        } else {
          ElMessage.error(result?.message)
        }
      })
    } else {
      fileListRemove(file)
    }
  }

  /**
   * 移除文件
   * @param file
   */
  function fileListRemove(file: any) {
    arrayRemoveByPropertyValue(state.fileList, 'uid', file.uid)
  }

  //////////////////////////////////////////////////
  // 文件回显
  //////////////////////////////////////////////////

  /**
   * 回显文件
   * params: { fileIds: '', bizId: '', bizType: '' }
   */
  function loadData(params: any) {
    apiFileSearch(params).then((result) => {
      if (result?.success) {
        state.fileList = []

        result.data.forEach((file) => {
          state.fileList.push({
            name: file.origName,
            percentage: 100,
            size: 0,
            status: 'success',
            uid: file.id,
            raw: {
              bizType: file.bizType,
              bizTypeName: file.bizTypeName,
              uid: file.id,
              name: file.origName,
              size: 0,
              type: file.fileType
            },
            response: {
              success: true,
              status: 200,
              data: [assign({ fileId: file.id }, file)]
            }
          })
        })
      } else {
        log.warn(result?.message || '')
      }
    })
  }

  /**
   * 获取文件ID字符串
   */
  function getFileIds() {
    const fileIds: Array<string> = []
    if (state.fileList && state.fileList.length > 0) {
      state.fileList.forEach((file: any) => {
        let myFile: any = null

        if (file.response.data && file.response.data.length > 0) {
          myFile = file.response.data[0]
        }

        if (myFile) {
          fileIds.push(myFile.fileId)
        }
      })
    }
    return fileIds.join(',')
  }

  /**
   * 获取文件集合
   */
  function getFiles() {
    const fileList: Array<any> = []
    if (state.fileList && state.fileList.length > 0) {
      state.fileList.forEach((file: any) => {
        let myFile = null

        if (file.response.data && file.response.data.length > 0) {
          myFile = file.response.data[0]
        }

        if (myFile) {
          fileList.push(myFile)
        }
      })
    }
    return fileList
  }

  /**
   * 文件上传校验
   * 全部上传完成返回true，反之返回false
   */
  function validate() {
    let result: boolean = true
    if (state.fileList && state.fileList.length > 0) {
      state.fileList.forEach((file: any) => {
        let myFile = null

        if (file.response.data && file.response.data.length > 0) {
          myFile = file.response.data[0]
        }

        if (!myFile) {
          result = false
        }
      })
    }
    return result
  }
  //////////////////////////////////////////////////
  // 暴露出去，父组件才可以调用到
  //////////////////////////////////////////////////
  defineExpose({
    loadData,
    getFileIds,
    getFiles,
    validate
  })
</script>

<style scoped lang="scss">
  .tw-upload-wrapper {
    width: 100%;
    .header {
      .tip {
        font-size: 12px;
        color: #606266;
        margin-top: 8px;
      }

      .toolbar {
        display: flex;
        align-items: center;

        .file-biz-type {
          margin-right: 8px;
        }

        .btns {
          display: flex;
          align-items: center;

          .icon-upload {
            margin-right: 3px;
          }
        }
      }
    }

    .content {
      margin-top: 10px;

      .btn-group {
        display: flex;
        align-items: center;

        .btn {
          padding: 3px 5px;
          cursor: pointer;
          color: #409eff;

          &:hover {
            color: #79bbff;
          }
        }
      }
    }
  }
</style>
